﻿using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.IO.Ports;
using System.Management;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenHardwareMonitor.Hardware;

namespace HardwareMonitor
{
    public partial class frmMain : Form
	{
        public static string version = "0.0.9.1";

        #region define variables
        SerialPort serialport = new SerialPort();
        NotifyIcon notifyicon;

        PerformanceCounterCategory gpu_engine_category = new PerformanceCounterCategory("GPU Engine");
        PerformanceCounter physical_disk_idle_time = new PerformanceCounter("PhysicalDisk", "% Idle Time", "_Total");

        Computer computer;
        UpdateVisitor updateVisitor;

        IHardware cpu, ram, gpu;
        ISensor cpu_temp, cpu_load, ram_load, ram_rest, ram_used, gpu_temp;

        bool is_auto_refresh_task_running = false;
		#endregion

		public frmMain()
        {
            InitializeComponent();

            Text = $"{Application.ProductName} - v{version}";

            notifyicon = new NotifyIcon() {
                Text = Application.ProductName,
                Icon = Properties.Resources.icon,
                Visible = true
            };
            notifyicon.MouseDoubleClick += new MouseEventHandler(notifyicon_MouseDoubleClick);

            initialize_serialport();
            initialize_hardware();
            get_hardware_and_sensor();
        }

        #region initialize hardware & serialport
        private void initialize_hardware()
        {
            updateVisitor = new UpdateVisitor();
            computer = new Computer() {
                // MainboardEnabled = true,
                CPUEnabled = true,
                RAMEnabled = true,
                // GPUEnabled = true,
                // HDDEnabled = true,
            };

            computer.Open();
            computer.Accept(updateVisitor);
        }

        private void initialize_serialport()
        {
            comboboxSerialPorts.Items.Clear();
            comboboxSerialPorts.Tag = "";

            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher
                ("select * from Win32_PnPEntity where Name like '%(COM%'")) {
                foreach (ManagementObject hardware_info in searcher.Get()) {
                    if (hardware_info.Properties["Name"].Value != null) {
                        string port_name = hardware_info.Properties["Name"].Value.ToString();

                        if (!port_name.Contains("(COM1)")) {
                            comboboxSerialPorts.Items.Add(port_name);
                        }
                    }
                }
            }

            buttonStart.Enabled = false;
            buttonStop.Enabled = false;

            if (comboboxSerialPorts.Items.Count > 0) {
                comboboxSerialPorts.SelectedIndex = 0;

                buttonStart.Enabled = true;
            }
        }

        private void get_hardware_and_sensor()
        {
            foreach (IHardware hardware in computer.Hardware) {
                switch (hardware.HardwareType) {
                    case HardwareType.CPU:
                        cpu = hardware;

                        foreach (ISensor sensor in cpu.Sensors) {
                            if (sensor.SensorType == SensorType.Temperature) {
                                cpu_temp = sensor;
                            }
                            else if (sensor.SensorType == SensorType.Load && sensor.Name.Equals("CPU Total")) {
                                cpu_load = sensor;
                            }
                        }
                        break;
                    case HardwareType.RAM:
                        ram = hardware;

                        foreach (ISensor sensor in ram.Sensors) {
                            if (sensor.SensorType == SensorType.Load) {
                                ram_load = sensor;
                            }
                            else if (sensor.SensorType == SensorType.Data && sensor.Name.Equals("Used Memory")) {
                                ram_used = sensor;
                            }
                            else if (sensor.SensorType == SensorType.Data && sensor.Name.Equals("Available Memory")) {
                                ram_rest = sensor;
                            }
                        }
                        break;
                    case HardwareType.GpuAti:
                    case HardwareType.GpuNvidia:
                        gpu = hardware;

                        foreach (ISensor sensor in gpu.Sensors) {
                            if (sensor.SensorType == SensorType.Temperature) {
                                gpu_temp = sensor;
                            }
                        }
                        break;
                }
            }
        }
        #endregion

        #region set control properties
        private void update_control_text(Control control, string text)
        {
            if (control.InvokeRequired) {
                Action<string> action = (x) => {control.Text = x;};
                control.Invoke(action, text);
			} else {
                control.Text = text;
			}
		}

        private void update_control_enabled(Control control, bool enabled)
        {
            if (control.InvokeRequired) {
                Action<bool> action = (x) => {control.Enabled = x;};
                control.Invoke(action, enabled);
			} else {
                control.Enabled = enabled;
			}
		}
		#endregion

		#region collect & calculate gpu samples
		private ArrayList collect_gpu_samples()
        {
            ArrayList samplesList = new ArrayList();

            foreach (var name in gpu_engine_category.GetInstanceNames()) {
                //if (gpu_engine_category.InstanceExists(name)) {
                try {
                    foreach (var counter in gpu_engine_category.GetCounters(name)) {
                        if (counter.CounterName.Equals("Utilization Percentage")) {
                            samplesList.Add(counter.NextSample());
                        }
                    }
                }
                catch (InvalidOperationException ioe) {
                    return samplesList;
                }
            }

            return samplesList;
        }

        private float calculate_gpu_sample(CounterSample sample1, CounterSample sample2)
        {
            long numerator = sample2.RawValue - sample1.RawValue;
            long denomenator = sample2.TimeStamp - sample1.TimeStamp;

            return (float)numerator / denomenator;
        }
        #endregion
		
        private void update_performance_values()
        {
            int cpu_load_value = 0;
            int cpu_temp_value = 0;
            int ram_load_value = 0;
            int disk_idle_time = 0;
            float gpu_load_value = 0;
            int gpu_temp_value = 0;

            update_control_text(labelHeaderTime, DateTime.Now.ToString("HH:mm"));

            #region calculate cpu usage & temp
            if (cpu != null && cpu_load != null && cpu_temp != null) {
                cpu.Update();
                cpu_load_value = Convert.ToInt32(cpu_load.Value);
                cpu_temp_value = Convert.ToInt32(cpu_temp.Value);
                cpu_load_value = cpu_load_value > 100 ? 100 : cpu_load_value;
                cpu_temp_value = cpu_temp_value > 100 ? 100 : cpu_temp_value;
            }
            #endregion

            update_control_text(labelCPUUsage, cpu_load_value.ToString());
            update_control_text(labelCPUTemperature, cpu_temp_value.ToString());
            labelCPUUsage.ForeColor = cpu_temp_value <= 55 ? Color.White : cpu_temp_value <= 75 ? Color.Orange : Color.Red;

            #region calculate ram usage
            if (ram != null && ram_load != null) {
                ram.Update();
                ram_load_value = Convert.ToInt32(ram_load.Value);
                ram_load_value = ram_load_value > 100 ? 100 : ram_load_value;
            }
            #endregion

            update_control_text(labelRAMUsage, ram_load_value.ToString());

            #region calculate hdd usage
            disk_idle_time = 100 - Convert.ToInt32(physical_disk_idle_time.NextValue());
            disk_idle_time = disk_idle_time > 100 ? 100 : disk_idle_time;
            #endregion

            update_control_text(labelHDDUsage, disk_idle_time.ToString());

            #region calculate gpu usage
            ArrayList samplesList1 = collect_gpu_samples();
            Thread.Sleep(20);
            ArrayList samplesList2 = collect_gpu_samples();

            if (samplesList1.Count != samplesList2.Count) {return;}

            for (int i = 0; i < samplesList1.Count; i++) {
                gpu_load_value += calculate_gpu_sample((CounterSample)samplesList1[i], (CounterSample)samplesList2[i]);
            }

            gpu_load_value = Convert.ToInt32(gpu_load_value * 100);
            gpu_load_value = gpu_load_value > 100 ? 100 : gpu_load_value;
            #endregion

            update_control_text(labelGPUUsage, gpu_load_value.ToString());
        }

        private void auto_refresh_task()
        {
            while (is_auto_refresh_task_running) {
                if (serialport.IsOpen) {
                    update_performance_values();

                    string prefix = textboxDataFormatPrefix.Text;
                    string separator = textboxDataFormatSeparator.Text;
                    string cpu_usage = labelCPUUsage.Text + separator;
                    string cpu_temp = labelCPUTemperature.Text + separator;
                    string ram_usage = labelRAMUsage.Text + separator;
                    string hdd_usage = labelHDDUsage.Text + separator;
                    string gpu_usage = labelGPUUsage.Text + separator;
                    string time_now = labelHeaderTime.Text;

                    if (serialport.IsOpen) {
                        serialport.WriteLine($"{prefix}{cpu_usage}{cpu_temp}{ram_usage}{hdd_usage}{gpu_usage}{time_now}");
                    }
                } else {
                    is_auto_refresh_task_running = false;
                    set_controls_status(true);
                }

                Thread.Sleep((int)numericSendPeriod.Value);
            }
        }

		private void set_controls_status(bool status)
        {
            update_control_enabled(comboboxSerialPorts, status);
            update_control_enabled(buttonRefreshSerialPorts, status);
            update_control_enabled(buttonStart, status);
            update_control_enabled(buttonStop, !status);
            update_control_enabled(groupboxSetup, status);

            notifyicon.Text = Application.ProductName + (status ? "- Stopped" : " - Running");
        }

		#region system events
		private void notifyicon_MouseDoubleClick(object sender, MouseEventArgs e)
		{
            if (Visible) {
                Hide();
			} else {
                Show();
                WindowState = FormWindowState.Normal;
            }
        }

		private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
		{
            is_auto_refresh_task_running = false;
            notifyicon.Dispose();
		}

		private void frmMain_SizeChanged(object sender, EventArgs e)
		{
            if (WindowState == FormWindowState.Minimized) {Hide();}
		}

        private void buttonRefreshSerialPorts_Click(object sender, EventArgs e)
        {
            initialize_serialport();
        }

        private void comboboxSerialPorts_SelectedIndexChanged(object sender, EventArgs e)
        {
            Match match = Regex.Match(comboboxSerialPorts.SelectedItem.ToString(), "COM[0-9]\\d*");

            if (match.Success) {
                comboboxSerialPorts.Tag = match.Groups[0].Value;
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (serialport.IsOpen) {serialport.Close();}

            serialport.PortName = comboboxSerialPorts.Tag.ToString(); // comboboxSerialPorts.SelectedItem.ToString();
            serialport.BaudRate = 115200;

            if (textboxDataFormatPrefix.TextLength == 0) {textboxDataFormatPrefix.Text = "PM";}
            if (textboxDataFormatSeparator.TextLength == 0) {textboxDataFormatSeparator.Text = "||";}

            try {
                serialport.Open();

                set_controls_status(false);
                is_auto_refresh_task_running = true;

                new Task(auto_refresh_task).Start();
            } catch (UnauthorizedAccessException) {
				MessageBox.Show($"Serial port {serialport.PortName} unaccessable");
			} catch (IOException) {
                initialize_serialport();
                MessageBox.Show($"Serial port {serialport.PortName} not exists");
            }
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            if (serialport.IsOpen) { serialport.Close(); }

            set_controls_status(true);
            is_auto_refresh_task_running = false;
        }
		#endregion
	}

	public class UpdateVisitor : IVisitor
    {
        public void VisitComputer(IComputer computer)
        {
            computer.Traverse(this);
        }
        public void VisitHardware(IHardware hardware)
        {
            hardware.Update();
            foreach (IHardware subHardware in hardware.SubHardware)
                subHardware.Accept(this);
        }
        public void VisitSensor(ISensor sensor) { }
        public void VisitParameter(IParameter parameter) { }
    }
}
